package org.six11.olive3.net;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.antlr.runtime.*;
import org.antlr.runtime.tree.BaseTree;
import org.antlr.runtime.tree.CommonTree; // import org.six11.olive3.MessageListener;
import org.six11.util2.Debug;

import static org.six11.olive3.net.Message.*;

/**
 * The stream handler can be used by clients or servers to parse olive data streams. When messages
 * are found, events are generated and fired using the registered cannons.
 * 
 * @author Gabe Johnson <johnsogg@cmu.edu>
 */
public class OliveStreamHandler {

  public static void main(String[] args) throws FileNotFoundException, IOException,
      RecognitionException {

    OliveStreamHandler handler = new OliveStreamHandler();
    for (String in : args) {
      handler.handle(new FileInputStream(in), new PrintWriter(System.out));
    }
  }

  private Map<String, List<MessageHandler>> asynchMessageHandlers;
  private Map<String, List<MessageHandler>> synchMessageHandlers;
  private MessageHandler defaultHandler;

  public OliveStreamHandler() {
    synchMessageHandlers = new HashMap<String, List<MessageHandler>>();
    asynchMessageHandlers = new HashMap<String, List<MessageHandler>>();
    defaultHandler = new MessageHandler() {
      public void handleLater(Message m) {
        bug("No handler found for message type '" + m.name + "'");
        StringBuffer asynchKnown = new StringBuffer();
        StringBuffer synchKnown = new StringBuffer();
        for (String s : asynchMessageHandlers.keySet()) {
          asynchKnown.append(" " + s);
        }
        for (String s : synchMessageHandlers.keySet()) {
          synchKnown.append(" " + s);
        }
        bug("Asynchronous handlers known: " + asynchKnown.toString());
        bug("Synchronous handlers known: " + synchKnown.toString());
      }
    };
  }

  /**
   * Attempts to parse the input stream for properly formatted statements according to the Olive3.g
   * grammar. When valid statments are found, events are emitted to the appropriate handler.
   */
  public void handle(InputStream origin, PrintWriter out) throws IOException, RecognitionException {

    ANTLRInputStream input = new ANTLRInputStream(origin);
    Olive3Lexer lexer = new Olive3Lexer(input);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    Olive3Parser parser = new Olive3Parser(tokens);
    CommonTree tree = (CommonTree) parser.olive_stream().getTree();
    List<Message> messages = buildMessages(((CommonTree) tree).getChildren());

    // route the resulting messages when appropriate
    for (Message m : messages) {
      if (m == null) {
        bug("That message is totally null. How is that possible?");
      }
      boolean handlerFound = false;
      // look for asynchronous message handlers who want this particular message type
      if (asynchMessageHandlers.containsKey(m.name)) {
        List<MessageHandler> many = asynchMessageHandlers.get(m.name);
        for (MessageHandler h : many) {
          handlerFound = true;
          h.handleLater(m);
        }
      }
      // now look for synchronous message handlers who want this message type.
      if (synchMessageHandlers.containsKey(m.name)) {
        List<MessageHandler> many = synchMessageHandlers.get(m.name);
        for (MessageHandler h : many) {
          handlerFound = true;
          h.handleNow(m, out);
        }
      }
      // in the event that no handler is found, send it to the default handler. This is a catch-all.
      if (!handlerFound) {
        defaultHandler.handleLater(m);
      }
    }

  }

  /**
   * Assumes the given string contains well-formed Olive messages, and returns a list of them. This
   * does not route messages to handlers.
   */
  public static List<Message> parseMessagesFromString(String msgString) {
    List<Message> ret = new ArrayList<Message>();
    InputStream origin;
    try {
      origin = new ByteArrayInputStream(msgString.getBytes("UTF-8"));
      ANTLRInputStream input = new ANTLRInputStream(origin);
      Olive3Lexer lexer = new Olive3Lexer(input);
      CommonTokenStream tokens = new CommonTokenStream(lexer);
      Olive3Parser parser = new Olive3Parser(tokens);
      CommonTree tree = (CommonTree) parser.olive_stream().getTree();
      ret = buildMessages(((CommonTree) tree).getChildren());
    } catch (UnsupportedEncodingException ex) {
      ex.printStackTrace();
    } catch (IOException ex) {
      ex.printStackTrace();
    } catch (RecognitionException ex) {
      ex.printStackTrace();
    }
    return ret;
  }

  public void registerAsynchronousHandler(String name, MessageHandler messageHandler) {
    if (asynchMessageHandlers.containsKey(name) == false) {
      asynchMessageHandlers.put(name, new ArrayList<MessageHandler>());
    }
    asynchMessageHandlers.get(name).add(messageHandler);
  }

  public void registerSynchronousHandler(String name, MessageHandler messageHandler) {
    if (synchMessageHandlers.containsKey(name) == false) {
      synchMessageHandlers.put(name, new ArrayList<MessageHandler>());
    }
    synchMessageHandlers.get(name).add(messageHandler);
  }

  private static List<Message> buildMessages(List<BaseTree> children) {
    List<Message> ret = new ArrayList<Message>();
    if (children != null) {
      for (BaseTree t : children) {
        String name = t.getChild(0).getText();
        List<Param> params = parseParams(((CommonTree) t.getChild(1)).getChildren());
        List<Statement> statements = parseStatements(((CommonTree) t.getChild(2)).getChildren());
        Message msg = new Message(name, params, statements);
        ret.add(msg);
      }
    }
    return ret;
  }

  private void bug(BaseTree t) {
    System.out.println("Processing tree of type " + t.getType() + " with text: " + t.getText());
  }

  private void bug(String what) {
    Debug.out("OliveStreamHandler", what);
  }

  private static List<Statement> parseStatements(List<CommonTree> children) {
    List<Statement> ret = new ArrayList<Statement>();
    if (children != null) {
      for (CommonTree t : children) {
        Statement s = new Statement(t.getChild(0).getText(), // name
            parseRequired(((CommonTree) t.getChild(1)).getChildren()), // required
            parseParams(((CommonTree) t.getChild(2)).getChildren())); // optional
        ret.add(s);
      }
    }
    return ret;
  }

  private static List<Value> parseRequired(List<CommonTree> list) {
    List<Value> ret = new ArrayList<Value>();
    if (list != null) {
      for (CommonTree t : list) {
        Value v = new Value(t.getText());
        ret.add(v);
      }
    }
    return ret;
  }

  private static List<Param> parseParams(List<CommonTree> children) {
    List<Param> ret = new ArrayList<Param>();
    if (children != null) {
      for (CommonTree t : children) {
        Param p = new Param(t.getChild(0).getText(), t.getChild(1).getText());
        ret.add(p);
      }
    }
    return ret;
  }

}
